package cn.changeforyou.web.cloud.gateway.filter;

import cn.changeforyou.web.cloud.webBase.CommonConstant;
import cn.changeforyou.web.cloud.webBase.auth.AuthModel;
import cn.changeforyou.base.exception.CommonResultEnum;
import cn.changeforyou.web.cloud.gateway.cache.LocalCache;
import cn.changeforyou.web.cloud.rpc.service.auth.AuthRpc;
import cn.changeforyou.web.cloud.rpc.service.auth.dto.SourceInfoDTO;
import cn.changeforyou.web.cloud.rpc.service.auth.enums.OpenLevel;
import com.alibaba.dubbo.config.annotation.Reference;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;

/**
 * token 过滤器
 *
 * @author denny
 * @date 2019/12/12 13:55
 */
@Slf4j
@Component
public class LoginTokenFilter extends BaseFilter implements GlobalFilter, Ordered {

    private static final String AUTHORIZE_TOKEN = "Authorization";
    private static final String BEARER = "Bearer ";
    private static final int token_start_index = BEARER.length();

    private LocalCache localCache;
    private AuthRpc authRpc;
    private ObjectMapper objectMapper = new ObjectMapper();
    {
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT);
    }


    /**
     * token过滤
     *
     * @param exchange webFlux对象
     * @param chain    过滤链
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String urlPath = exchange.getRequest().getURI().getPath();
        ServerHttpResponse response = exchange.getResponse();

        //查看资源是否已注册
        SourceInfoDTO source = localCache.getSource(urlPath);
        if (null == source) {
            response.setStatusCode(HttpStatus.NOT_FOUND);
            return response.setComplete();
        }

        //认证
        log.info("当前环境已开启token校验");
        OpenLevel openLevel = source.getOpenLevel();
        if (openLevel == OpenLevel.open) {
            return chain.filter(exchange);
        }
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();

        String tokenHeader = headers.getFirst(AUTHORIZE_TOKEN);
        if (StringUtils.isBlank(tokenHeader)) {
            response.getHeaders().add("Content-Type", MediaType.APPLICATION_JSON_UTF8_VALUE);
            return super.reponseErrorInfo(response, CommonResultEnum.TOKEN_NOT_EXIST);
        }
        String token = tokenHeader.substring(token_start_index);
        log.info("token=" + token);
        AuthModel authModel = authRpc.parseToken(token);
//        if (openLevel == OpenLevel.halfOpen) {
//            return chain.filter(exchange);
//        }

        List<Integer> sourceIds = authModel.getSourceIds();
        Integer sourceId = source.getId();
        if (!sourceIds.contains(sourceId)) {
            response.setStatusCode(HttpStatus.FORBIDDEN);
            return response.setComplete();
        }

        //授权
        BodyInserter bodyInserter = BodyInserters.empty();
        HttpHeaders newHeader = new HttpHeaders();
        newHeader.putAll(headers);

        CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
        return bodyInserter.insert(outputMessage, new BodyInserterContext())
                .then(Mono.defer(() -> {
                    ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(
                            exchange.getRequest()) {
                        @Override
                        public HttpHeaders getHeaders() {
                            HttpHeaders httpHeaders = new HttpHeaders();
                            httpHeaders.putAll(super.getHeaders());
                            try {
                                String authStr = objectMapper.writeValueAsString(authModel);
                                String encodeStr = URLEncoder.encode(authStr, "UTF-8");
                                if (log.isDebugEnabled()) {
                                    log.debug("把权限json字符串放入header中:{}", authStr);
                                }
                                httpHeaders.add(CommonConstant.AUTH_HEADER_NAME, encodeStr);
                            } catch (JsonProcessingException e) {
                                e.printStackTrace();
                            } catch (UnsupportedEncodingException e) {
                                e.printStackTrace();
                            }
                            return httpHeaders;
                        }

                        @Override
                        public Flux<DataBuffer> getBody() {
                            return outputMessage.getBody();
                        }
                    };
                    return chain.filter(exchange.mutate().request(decorator).build());
                }));
    }


    @Override
    public int getOrder() {
        return 1;
    }

    @Autowired
    public void setLocalCache(LocalCache localCache) {
        this.localCache = localCache;
    }

    @Reference
    public void setAuthRpc(AuthRpc authRpc) {
        this.authRpc = authRpc;
    }
}
